package com.freewater.library.lending.reader.model;

import com.freewater.library.common.domain.BaseAggregateRoot;
import com.freewater.library.common.domain.Nullable;
import com.freewater.library.lending.reader.model.event.ReaderDisabledEvent;
import com.freewater.library.lending.reader.model.event.ReaderEnabledEvent;
import lombok.Getter;
import org.apache.commons.lang3.Validate;

import java.util.HashSet;
import java.util.Optional;
import java.util.Set;

/**
 * 读者
 */
@Getter
public class Reader extends BaseAggregateRoot {
    /**
     * 最多可以超期归还书籍数量，否则读者会被禁止借阅
     */
    public static final int MAX_OVERDUE_BOOK_COUNT = 3;
    /**
     * 最多可以占用的书籍数量，否则读者不能再借阅书籍
     */
    public static final int MAX_OCCUPY_BOOK_COUNT = 3;

    /**
     * 读者Id
     */
    private final Long id;
    /**
     * 读者姓名
     */
    private final String name;
    /**
     * 是否已被暂停借阅
     */
    private boolean suspended;
    /**
     * 占用的书（包括预定的书及借出的书，不包括借出后遗失的书）
     */
    private Set<OccupiedBook> occupiedBooks;
    /**
     * 逾期的书（不包括借出后遗失的书）
     */
    private Set<CheckedOutOverdueBook> checkedOutOverdueBooks;

    @Override
    public Long getId() {
        return this.id;
    }

    public Reader(Long id, String name) {
        this(id, name, false, null, null);
    }

    public Reader(Long id,
                  String name,
                  boolean suspended,
                  @Nullable Set<OccupiedBook> occupiedBooks,
                  @Nullable Set<CheckedOutOverdueBook> checkedOutOverdueBooks) {
        Validate.notNull(id, "读者Id不能为空");
        Validate.notBlank(name, "读者姓名不能为空");
        this.id = id;
        this.name = name;
        this.suspended = suspended;
        this.occupiedBooks = Optional.ofNullable(occupiedBooks).orElse(new HashSet<>());
        this.checkedOutOverdueBooks = Optional.ofNullable(checkedOutOverdueBooks).orElse(new HashSet<>());
    }

    /**
     * 预定书
     *
     * @param qrCode 二维码
     */
    public void reservedBook(String qrCode) {
        requireQrCode(qrCode);
        OccupiedBook occupiedBook = new OccupiedBook(qrCode);
        Set<OccupiedBook> occupiedBooks = this.getOccupiedBooks();
        if (occupiedBooks.contains(occupiedBook)) {
            return;
        }
        occupiedBooks.add(occupiedBook);
    }

    /**
     * 取消预定书
     *
     * @param qrCode 二维码
     */
    public void cancelReservedBook(String qrCode) {
        requireQrCode(qrCode);
        OccupiedBook occupiedBook = new OccupiedBook(qrCode);
        Set<OccupiedBook> occupiedBooks = this.getOccupiedBooks();
        if (!occupiedBooks.contains(occupiedBook)) {
            return;
        }
        occupiedBooks.remove(occupiedBook);
    }

    /**
     * 借出书
     *
     * @param qrCode 二维码
     */
    public void checkedOutBook(String qrCode) {
        requireQrCode(qrCode);
        OccupiedBook occupiedBook = new OccupiedBook(qrCode);
        Set<OccupiedBook> occupiedBooks = this.getOccupiedBooks();
        if (occupiedBooks.contains(occupiedBook)) {
            return;
        }
        occupiedBooks.add(occupiedBook);
    }

    /**
     * 借书逾期
     *
     * @param qrCode 二维码
     */
    public void checkedOutBookOverdue(String qrCode) {
        requireQrCode(qrCode);
        CheckedOutOverdueBook overdueBook = new CheckedOutOverdueBook(qrCode);
        Set<CheckedOutOverdueBook> overdueBooks = this.getCheckedOutOverdueBooks();
        if (overdueBooks.contains(overdueBook)) {
            return;
        }
        overdueBooks.add(overdueBook);
        // 超过最多可预期数量，则读者被暂停借书
        if (this.getOverdueTimes() >= MAX_OVERDUE_BOOK_COUNT) {
            this.disable();
        }
    }

    /**
     * 借出书遗失
     *
     * @param qrCode 二维码
     */
    public void checkedOutBookLost(String qrCode) {
        requireQrCode(qrCode);
        // 从当前占用书中移除
        OccupiedBook occupiedBook = new OccupiedBook(qrCode);
        Set<OccupiedBook> occupiedBooks = this.getOccupiedBooks();
        occupiedBooks.remove(occupiedBook);
        // 从逾期书列表中移除
        Set<CheckedOutOverdueBook> overdueBooks = this.getCheckedOutOverdueBooks();
        CheckedOutOverdueBook overdueBook = new CheckedOutOverdueBook(qrCode);
        overdueBooks.remove(overdueBook);
    }

    /**
     * 还书
     *
     * @param qrCode 二维码
     */
    public void returnedBackBook(String qrCode) {
        requireQrCode(qrCode);
        OccupiedBook occupiedBook = new OccupiedBook(qrCode);
        Set<OccupiedBook> occupiedBooks = this.getOccupiedBooks();
        if (!occupiedBooks.contains(occupiedBook)) {
            return;
        }
        occupiedBooks.remove(occupiedBook);
    }

    /**
     * 暂停读者借阅
     */
    public void disable() {
        if (suspended) {
            return;
        }
        this.suspended = true;
        this.raiseEvent(new ReaderDisabledEvent(this.id));
    }

    /**
     * 允许读者借阅
     */
    public void enable() {
        if (!suspended) {
            return;
        }
        // 清空逾期未还书列表
        this.checkedOutOverdueBooks = new HashSet<>();
        this.suspended = false;
        this.raiseEvent(new ReaderEnabledEvent(this.id));
    }

    /**
     * 获取累计逾期归还书数量
     *
     * @return 累计逾期归还书数量
     */
    public int getOverdueTimes() {
        return this.getCheckedOutOverdueBooks().size();
    }

    /**
     * 获取占有书数量：预定书数量 + 借出书数量
     *
     * @return 占有书数量
     */
    public int getBooksOccupied() {
        return this.getOccupiedBooks().size();
    }

    private void requireQrCode(String qrCode) {
        Validate.notBlank(qrCode, "二维码不能为空");
    }
}
